#include "Ast.h"					
#include "ParserUtil.h"					

#define CINFO	yylinenum, yycolumnno, yyfilename
/****************************************************************/
extern const OpNode::OpInfo opInfo[] = {
	// print name, arity, paren_flag, fixity, arg types, out type, constraints
	//
	// Paren_flag -- opnode->print() outputs is surrounded by parenthesis if 
	// this flag is set. As set below, the expression may not print correctly
	// in some rare cases, e.g., ~(b * c) will get printed as ~b * c,
	// which actually corresponds to (~b)*c. To ensure that things get printed
	// correctly all the time, more paren_flags should be set to 1, but this
	// will lead to more clutter in printed output. Basically, what we have done
	// here is to look are expressions by type -- arithmetic, relational, 
	// boolean, bit operations, etc. Within each type, the highest priority 
	// operator is printed without paren. This will work correctly, as long
	// as the language doesn't permit mixing of different types of expressions.
	// But this assumption doesn't always hold, as in the example above. Also,
	// there is an exception to this general approach in the case of unary minus
	// and * -- since (-a)*b and -(a*b) have the same meaning, we can exclude
	// paren for * without an error.
	//
	// Codes for constraints:
	// first character:
	//    N: No additional constraint over what is given by argTypes
	//    I: all arguments must have identical type
	//    S: one of the arguments must have a type that is a supertype of
	//        of all other arguments. All other arguments require a coercion 
	//        operation to be introduced so as to convert their type to S.
	//    s: one of the arguments must have a type that is a subtype of
	//        of all other arguments. 
	//    L: all list arguments (and list output) must have same type. In 
	//        addition, all non-list arguments (and output) must have same 
	//        type as that of elements in these lists
	//    T: all tuple arguments to the function must have same type.
	//    A: (assignment). Type of second argument must be a subtype of
	//       the first argument
	//
	// second character:
	//    O: output type is the same as out type. (In the following cases,
	//        the output type need not be equal to out type, but a subtype
	//        of it.) Since a TypeTag provides complete type information only
	//        for primitive types, `O' is applicable only in this case.
	//    digit: output type is the same as that of the digit'th argument
	//       In this case, a third character may be used, the code for
	//       which is as follows:
	//         'e' denotes that the output is of type alpha, where
	//             the type of digit'th argument is list(alpha)
	//         'l' denotes that the output is of type list(alpha), where
	//             alpha is the type of the digit'th argument.
	//    S: The output type is the same as that of the argument with the
	//        most general type. (Typically used with first character 'S')
	//    s: The output type is the same as that of the argument with the
	//        least general type. (Typically used with first character 'S')
	//    P: The output type is the product of the types of all arguments
	//    p: The output type is a component of the input tuple type. The
	//        following character specifies the component. A digit k specifies
	//        that the component number as k. The character 'a' indicates that
	//        the component number is given by an integer argument to the
	//        operator. The argument number is given by the following digit.
	//        'p' can be used only in conjunction with first character 'P'.
	//    L: Output type is the same as type of list arguments. Can be used
	//        only in conjunction with first character L.
	//    e: Output type is the same as type of element of list arguments. 
	//        Can be used only in conjunction with first character L.
	//
	{OpNode::UMINUS, "-",  1, 0, OpNode::PREFIX, {Type::SIGNED}, Type::SIGNED, "N1"},
	{OpNode::PLUS, "+",  2, 1, OpNode::INFIX, {Type::NUMERIC, Type::NUMERIC}, Type::NUMERIC, "SS"},
	{OpNode::MINUS, "-",  2, 1, OpNode::INFIX, {Type::NUMERIC, Type::NUMERIC}, Type::NUMERIC, "SS"},
	{OpNode::MULT, "*",  2, 0, OpNode::INFIX, {Type::NUMERIC, Type::NUMERIC}, Type::NUMERIC, "SS"},
	{OpNode::DIV, "/",  2, 1, OpNode::INFIX, {Type::NUMERIC, Type::NUMERIC}, Type::NUMERIC, "SS"},
	{OpNode::MOD, "%",  2, 1, OpNode::INFIX, {Type::INTEGRAL, Type::INTEGRAL}, Type::INTEGRAL, "S2"},
	{OpNode::EQ, "==", 2, 0, OpNode::INFIX, {Type::PRIMITIVE, Type::PRIMITIVE}, Type::BOOL, "SO"},
	{OpNode::NE, "!=", 2, 0, OpNode::INFIX, {Type::PRIMITIVE, Type::PRIMITIVE}, Type::BOOL, "SO"},
	{OpNode::GT, ">",  2, 0, OpNode::INFIX, {Type::SCALAR, Type::SCALAR}, Type::BOOL, "SO"},
	{OpNode::LT, "<",  2, 0, OpNode::INFIX, {Type::SCALAR, Type::SCALAR}, Type::BOOL, "SO"},
	{OpNode::GE, ">=", 2, 0, OpNode::INFIX, {Type::SCALAR, Type::SCALAR}, Type::BOOL, "SO"},
	{OpNode::LE, "<=", 2, 0, OpNode::INFIX, {Type::SCALAR, Type::SCALAR}, Type::BOOL, "SO"},
	{OpNode::AND, "&&",  2, 1, OpNode::INFIX, {Type::BOOL, Type::BOOL}, Type::BOOL, "NO"},
	{OpNode::OR, "||",  2, 1, OpNode::INFIX, {Type::BOOL, Type::BOOL}, Type::BOOL, "NO"},
	{OpNode::NOT, "!",  1, 0, OpNode::PREFIX, {Type::BOOL}, Type::BOOL, "NO"}, 
	{OpNode::BITNOT, "~",  1, 0, OpNode::PREFIX, {Type::INTEGRAL}, Type::INTEGRAL, "N1"},
	{OpNode::BITAND, "&",  2, 1, OpNode::INFIX, {Type::INTEGRAL, Type::INTEGRAL}, Type::INTEGRAL, "Ss"},
	{OpNode::BITOR, "|",  2, 1, OpNode::INFIX, {Type::INTEGRAL, Type::INTEGRAL}, Type::INTEGRAL, "SS"},
	{OpNode::BITXOR, "^",  2, 0, OpNode::INFIX, {Type::INTEGRAL, Type::INTEGRAL}, Type::INTEGRAL, "SS"},
	{OpNode::SHL, "<<", 2, 1, OpNode::INFIX, {Type::INTEGRAL, Type::INTEGRAL}, Type::INTEGRAL, "N1"},
	{OpNode::SHR, ">>", 2, 1, OpNode::INFIX, {Type::INTEGRAL, Type::INTEGRAL}, Type::INTEGRAL, "N1"},
	{OpNode::ASSIGN, "=",  2, 0, OpNode::INFIX, {Type::NATIVE, Type::NATIVE}, Type::VOID, "AO"},
	{OpNode::PRINT, "print", OpNode::VARIABLE, 1, OpNode::PREFIX, {Type::NATIVE}, Type::VOID, "NO"},
	{OpNode::INVALID, "invalid",            0, 0, OpNode::PREFIX, {}, Type::ERROR, "NO"}
};

OpNode::OpNode(OpCode op, ExprNode* a1, ExprNode* a2, 
		int ln, int col, string file):
	ExprNode(ExprNode::OP_NODE, NULL, ln,col,file) {
		opCode_ = op;
		if (a1 != NULL) {
			arity_ = 1;
			arg_.push_back(a1);
			if (a2 != NULL) {
				arity_++;
				arg_.push_back(a2);
			}
		}
	}

OpNode::OpNode(const OpNode &other):
	ExprNode(other) {
		arity_ = other.arity();
		opCode_ = other.opCode();
		for (unsigned int i=0; (i < other.arity()); i++) {
			if (other.arg_[i]) {
				arg_.push_back((other.arg_[i])->clone());
			} 
			else {
				arg_.push_back(NULL);
			}
		}
	}

void 
OpNode::print(ostream& os, int indent) const {
	if (opInfo[opCode_].prtType_ == OpNode::PREFIX) {
		os << opInfo[opCode_].name_;
		if (arity_ > 0) {
			if (opInfo[opCode_].needParen_) 
				os << '(';
			for (unsigned i=0; i < arity_-1; i++) {
				if (arg_[i])
					arg_[i]->print(os, indent);
				else os << "NULL";
				os << ", ";
			}
			if (arg_[arity_-1])
				arg_[arity_-1]->print(os, indent);
			else os << "NULL";
			if (opInfo[opCode_].needParen_) 
				os << ") ";
		}
	}
	else if ((opInfo[opCode_].prtType_ == OpNode::INFIX) && (arity_ == 2)) {
		if (opInfo[opCode_].needParen_) 
			os << "(";
		if(arg_[0])
			arg_[0]->print(os, indent);
		else os << "NULL";
		os << opInfo[opCode_].name_; 
		if(arg_[1])
			arg_[1]->print(os, indent);
		else os << "NULL";
		if (opInfo[opCode_].needParen_) 
			os << ")";
	}
	else internalErr("Unhandled case in OpNnode::print");
}

// AstNode implemenation
AstNode::AstNode(NodeType nt, int line, int column, string file) : 
	ProgramElem(NULL, line, column, file) {
}
AstNode::AstNode(const AstNode& node) { // copy constructor 
	nodeType_ = node.nodeType_;
}

// ExprNode implementation
ExprNode::ExprNode(ExprNodeType et, const Value* val, int line, int column,
		string file) : AstNode(EXPR_NODE, line, column, file) {
	val_ = val;
	exprType_ = et;
}
ExprNode::ExprNode(const ExprNode& node) : AstNode(node) {
	val_ = node.val_;
	coercedType_ = node.coercedType_;
}

// RefExprNode impl.
RefExprNode::RefExprNode(string ext, const SymTabEntry* ste, 
		int line, int column, string file) :
			ExprNode(REF_EXPR_NODE, 0, CINFO) {
	ext_ = ext;
	sym_ = ste;
}

RefExprNode::RefExprNode(const RefExprNode& node) : 
		ExprNode(REF_EXPR_NODE, 0, node.line(), node.column(), node.file()) {
	ext_ = node.ext_;
	sym_ = node.sym_;
}

void RefExprNode::print(ostream &os, int indent) const {
	os << ext();
}

// ValueNode
void ValueNode::print(ostream& os, int indent) const {
	value()->print(os,indent);
}


// PrimitivePatNode
PrimitivePatNode::PrimitivePatNode(EventEntry* ee, vector<VariableEntry*>* params, 
		ExprNode* c,int line, int column, string file) :
		BasePatNode(PRIMITIVE, CINFO)
{
	ee_ = ee;
	cond_ = c;
	params_ = params;
	if (params) {
		vector<VariableEntry*>::iterator it = params->begin();
		for (;it!=params->end();it++) {
			if (stm.lookUpInScope((*it)->name(), SymTabEntry::BLOCK_KIND)) {
				string _err = "Redefinition of name ";
				_err += (*it)->name();
				errMsg(_err);
			} else
				stm.insert((*it));
		}
	}
	if (ee_ && params_) {
		Type* type = ee_->type();
		vector<Type*>* argTypes = NULL;
		if (type) argTypes = type->argTypes();
		if (argTypes && params_->size() > argTypes->size()) {
			string _err = ee_->name();
			_err += ": mismatch in the number of arguments";
			errMsg(_err);
		}
	}
}

void PrimitivePatNode::print(ostream& os, int indent) const {
	if (!ee_) return;
	Type* type = ee_->type();
	vector<Type*>* argTypes = NULL;
	if (type) argTypes = type->argTypes();

	os << "(" << ee_->name();
	if (params_) {
		os << "(";
		vector<VariableEntry*>::const_iterator it;
		vector<Type*>::const_iterator itType;
		for (it = params_->begin(),itType = argTypes->begin();it!=params_->end();) {
			if ((*itType) && (*it))
				os << (*itType)->fullName() << " " <<  (*it)->name();
			++it;
			++itType;
			if (it!=params_->end() && itType!=argTypes->end())
				os << ", ";
		}
		os << ")";
	} else {
		if (ee_->name()!="any")
			os << "()";
	}
	if (cond_) {
		os << "|";
		cond_->print(os,indent);
	}
	os << ")";
}

bool PrimitivePatNode::hasSeqOps() const {
	return false;
}
bool PrimitivePatNode::hasNeg() const {
	return false;
}
bool PrimitivePatNode::hasAnyOrOther() const {
	return false;
}

// CompoundStmtNode
void CompoundStmtNode::printWithoutBraces(ostream& os, int indent) const {
	if (stmts_ == NULL) return;
	list<StmtNode*>* sm = stmts_;
	list<StmtNode*>::iterator it = sm->begin();
	while(1) {
		if (*it) {
			(*it)->print(os,indent);
			if ( (*it)->stmtNodeKind() == StmtNode::EXPR || (*it)->stmtNodeKind() == StmtNode::RETURN ) {
				os << ";";
				++it;
				if ( it == sm->end() )
					prtln(os,indent-STEP_INDENT);
				else
					prtln(os,indent);
				--it;
			}
			it++;
			if (it==sm->end()) 
				break;
		} else {
			break;
		}
	};
}
void CompoundStmtNode::print(ostream& os, int indent) const {
	os << "{";
	if (stmts_ && stmts_->size()) {
		prtln(os,indent+STEP_INDENT);
		printWithoutBraces(os,indent+STEP_INDENT);
	}
	os << "};";
	prtln(os,indent);
}

// PatNode
PatNode::PatNode(int line, int column, string file) : BasePatNode(UNDEFINED, CINFO) {
}

PatNode::PatNode(PatNodeKind pk, BasePatNode *p1, BasePatNode*p2, int line, int column, string file) : BasePatNode(pk, CINFO) {
	pat1_ = p1;
	pat2_ = p2;
	if (pk == NEG && pat1_ && 
		( pat1_->hasNeg() || pat1_->kind() != PatNode::PRIMITIVE )) {
		string err_ = "Only simple patterns without `.', `*', and `!' operatorscan be negated";
		errMsg(err_);
	}
}

bool PatNode::hasNeg() const {
	if ( (pat1_ || pat2_) && kind() == NEG)
		return true;
	if (pat1_ && pat1_->hasNeg()) return true;
	if (pat2_ && pat2_->hasNeg()) return true;
	return false;
}
bool PatNode::hasSeqOps() const {
	if ( (pat1_ && pat2_) && kind() == SEQ)
		return true;
	if (pat1_ && pat1_->hasSeqOps()) return true;
	if (pat2_ && pat2_->hasSeqOps()) return true;
	return false;
}
bool PatNode::hasAnyOrOther() const {
	if ( (pat1_ && pat2_) && kind() == OR)
		return true;
	if (pat1_ && pat1_->kind()!=PRIMITIVE) return true;
	if (pat2_ && pat2_->kind()!=PRIMITIVE) return true;
	return false;
}
void PatNode::print(ostream& os, int indent) const {
	os << "(";
	switch(kind()) {
	case PRIMITIVE:
		break;
	case EMPTY:
		break;
	case NEG:
		os << "!";
		pat1_->print(os,indent);
		break;
	case SEQ:
		pat1_->print(os,indent);
		os << ":";
		pat2_->print(os,indent);
		break;
	case OR:
		pat1_->print(os,indent);
		os << " \\/ ";
		pat2_->print(os,indent);
		break;
	case STAR:
		pat1_->print(os,indent);
		os << "**";
		break;
	case UNDEFINED:
		break;
	};
	os << ")";
}

// RuleNode
RuleNode::RuleNode(BlockEntry *re,
		BasePatNode* pat, StmtNode* reaction, 
		int line, int column, string file) : 
	AstNode(AstNode::RULE_NODE, CINFO) {
		rste_ = re;
		pat_ = pat;
		reaction_ = reaction;
	}

void RuleNode::print(ostream& os, int indent) const {
	if (pat_) {
		prtln(os,indent);
		pat_->print(os,indent);
		os << "-->  ";
	}
	if (reaction_) {
		reaction_->print(os,indent);
	};
	os << ";;";
}

// InvocationNode
InvocationNode::InvocationNode(const SymTabEntry *ste, vector<ExprNode*>* param, 
		int line, int column, string file) : ExprNode(INV_NODE, NULL, CINFO) {
	params_ = param;
	ste_ = ste;
}

InvocationNode::InvocationNode(const InvocationNode& inv) : ExprNode(inv) {
	params_ = inv.params_;
	ste_ = inv.ste_;
}

void InvocationNode::print(ostream& os, int indent) const {
	os << ste_->name() << "(";
	if (params_) {
		vector<ExprNode*>::const_iterator it;
		for (it=params_->begin(); ;) {
			if (*it) {
				(*it)->print(os, indent);
			}
			++it;
			if (it!=(params_->end())) {
				os << ",";
			} else 
				break;
		}
	}
	os << ")";
}

// IfNode

IfNode::IfNode(ExprNode* cond, StmtNode* thenStmt, 
		StmtNode* elseStmt, int line, int column, string file) : 
	StmtNode(StmtNode::IF, CINFO) {
	cond_ = cond;
	then_ = thenStmt;
	else_ = elseStmt;
}

void IfNode::print(ostream& os, int indent) const {
	os << "if (";
	cond_->print(os,indent);
	os << ") ";
	if (then_) {
		then_->print(os,indent);
		if (then_->stmtNodeKind() != StmtNode::COMPOUND) {
			os << ";";
			if (!else_) prtln(os,indent);
		}
	}
	if (else_) {
		if (then_->stmtNodeKind() != StmtNode::COMPOUND)
			prtln(os, indent);
		os << "else ";
		else_->print(os,indent);
		if (else_->stmtNodeKind() != StmtNode::COMPOUND) {
			os << ";";
			prtln(os, indent);
		}
	}
}

